<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>动态高度</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      ul,
      ol {
        list-style: none;
      }

      html,
      body {
        height: 100%;
      }

      body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        gap: 20px;
      }

      .container {
        height: 500px;
        border: 1px solid;
        overflow-y: scroll;
      }

      .list {
        width: 500px;
        box-sizing: border-box;
      }

      .item {
        display: flex;
        justify-content: center;
        align-items: center;
        border-bottom: 1px solid gray;
        box-sizing: border-box;
      }
    </style>
  </head>
  <body>
    <!-- https://juejin.cn/post/7415663559310540827 -->
    <button class="btn">渲染数据</button>
    <div class="container">
      <ul class="list">
        <li class="item">1</li>
      </ul>
    </div>
    <script>
      // 问题：动态高度由于需要渲染之后才能获取实际高度，会导致滚动出现小的跳动，还是存在交互体验的问题
      // 解决：不预设高度了，浪费下用户的时间，因为我们知道其实动态高度一般源于异步数据，本身就存在时间等待的问题，我想如果把数据请求了，然后分批渲染到浏览器上，就可以获取动态高度来填充 cache，这样就不需要预设高度，在使用cache计算全是真实高度了
      const $btn = document.querySelector(".btn");
      const $list = document.querySelector(".list");
      const $container = document.querySelector(".container");
      const datas = new Array(100000).fill(0).map((v, i) => [
        // 动态高度
        Math.max(Math.floor(Math.random() * 100) + 28, 57),
        `${i}#${Math.random().toString(36).slice(2)}`,
        i,
      ]);

      const fixHeight = 57;
      let startIndex = 0;
      const pageSize = 20;
      // const pageTotal = Math.ceil(datas.length / pageSize); // 总页数
      const cache = {};
      let totalHeight = fixHeight * datas.length;

      // function render() {
      //   $list.innerHTML = datas
      //     .map((d, i) => `<li class="item">${i}</li>`)
      //     .join("");
      // }

      function render() {
        let top = 0;
        for (let i = 0; i < startIndex; i++) {
          top += cache[i] ?? fixHeight;
        }
        $list.style.paddingTop = `${top}px`;
        $list.innerHTML = datas
          .slice(startIndex, startIndex + pageSize)
          .map(([h, d, i]) => {
            return `<li class="item" style="height: ${h}px" data-i="${i}">${d}</li>`;
          })
          .join("");
        // 模拟 vue 中 nextTick
        // requestAnimationFrame(() => {
        //   Array.from($list.children).forEach((c) => {
        //     if (typeof cache[c.dataset.i] === "undefined") {
        //       cache[c.dataset.i] = c.offsetHeight;
        //       totalHeight = totalHeight - fixHeight + c.offsetHeight;
        //     }
        //   });
        //   // 更新高度
        //   $list.style.height = `${totalHeight}px`;
        // });
      }

      async function render0(pageNum) {
        $list.innerHTML = datas
          .slice(pageNum, pageNum + pageSize)
          .map(([h, d, i]) => {
            return `<li class="item" style="height: ${h}px" data-i="${i}">${d}</li>`;
          })
          .join("");

        return new Promise((resolve) => {
          requestAnimationFrame(() => {
            Array.from($list.children).forEach((c) => {
              if (typeof cache[c.dataset.i] === "undefined") {
                cache[c.dataset.i] = c.offsetHeight;
                totalHeight = totalHeight - fixHeight + c.offsetHeight;
              }
            });
            $list.style.height = `${totalHeight}px`;
            resolve();
          });
        });
      }

      // const tasks = new Array(pageTotal).fill(0).map((v, i) => {
      //   datas.slice();
      //   return () => {};
      // });

      // 按钮模拟异步请求
      $btn.addEventListener("click", async function (e) {
        // $list.style.height = `${totalHeight}px`;
        // render();

        $list.style.opacity = 0.2;
        const pageSize = 100;
        const total = Math.ceil(datas.length / pageSize);
        for (let pageNum = 0; pageNum < total; pageNum++) {
          await render0(pageNum, pageSize);
        }

        // end
        $list.style.opacity = 1;
        render();

        // 利用浏览器空闲时间来计算 dom 的动态高度
        // requestIdleCallback((deadline) => {});
      });

      $container.addEventListener("scroll", function (e) {
        const scrollTop = this.scrollTop; // 已滚动高度
        let offsetHeight = 0;

        for (let i = 0; i < datas.length; i++) {
          offsetHeight += cache[i] ?? fixHeight;
          if (offsetHeight > scrollTop) {
            startIndex = i;
            break;
          }
        }
        render();
      });
    </script>
  </body>
</html>
